package com.dl698.asn1.axdr;

import com.dl698.util.DataUtils;
import com.dl698.util.ReverseByteArrayInputStream;
import com.dl698.util.ReverseByteArrayOutputStream;

import java.io.IOException;

/**
 * AXDR整数类型
 * 支持有符号和无符号整数的编解码
 * 
 * @author wind
 * @version 1.1.1
 */
public class AxdrInteger implements AxdrType {

    private boolean lengthFixed;
    private boolean unsigned;
    private long val = 0;
    private long maxVal = 0;
    private long minVal = 0;
    private byte[] dataCode = null;

    /**
     * 默认构造函数
     */
    public AxdrInteger() {
        this.lengthFixed = false;
        this.unsigned = false;
    }

    /**
     * 设置值
     * 
     * @param val 整数值
     */
    public void setVal(long val) {
        this.lengthFixed = false;
        this.unsigned = false;
        setValue(val);
    }

    /**
     * 设置数据编码
     * 
     * @param dataCode 编码数据
     */
    public void setDataCode(byte[] dataCode) {
        this.lengthFixed = false;
        this.unsigned = false;
        this.dataCode = dataCode;
    }

    /**
     * 设置最小值、最大值和当前值
     * 
     * @param min 最小值
     * @param max 最大值
     * @param val 当前值
     */
    public void setMinMaxVal(long min, long max, long val) {
        this.minVal = min;
        this.maxVal = max;
        setValue(val);
        this.lengthFixed = true;
        this.unsigned = (min >= 0);
    }

    @Override
    public int decode(ReverseByteArrayInputStream input) {
        try {
            int codeLength;
            byte[] byteCode;
            int length;

            if (lengthFixed) {
                length = numOfBytesOf(minVal, maxVal);
                codeLength = length;
            } else {
                length = input.read();
                if ((length & 0x80) == 0x80) {
                    length = (length ^ 0x80);
                    codeLength = length + 1;
                } else {
                    this.val = length;
                    return 1;
                }
            }

            byteCode = new byte[length];
            DataUtils.readFully(input, byteCode, 0, byteCode.length);

            if ((byteCode[0] & 0x80) == 0x80 && !unsigned) {
                // 负数处理
                val = -1;
                for (int i = 0; i < length; i++) {
                    int numShiftBits = 8 * (length - i - 1);
                    val &= ((long) (byteCode[i] & 0xFF) << numShiftBits) | ~(0xFFL << numShiftBits);
                }
            } else {
                // 正数处理
                val = 0;
                for (int i = 0; i < length; i++) {
                    val |= ((long) (byteCode[i] & 0xFF)) << (8 * (length - i - 1));
                }
            }

            return codeLength;
        } catch (IOException e) {
            throw new RuntimeException("Decode error", e);
        }
    }

    /**
     * 编码并保存
     * 
     * @param encodingSizeGuess 编码大小预估
     */
    public void encodeAndSave(int encodingSizeGuess) {
        ReverseByteArrayOutputStream revOStream = new ReverseByteArrayOutputStream();
        revOStream.setBufSize(encodingSizeGuess);
        encode(revOStream);
        this.dataCode = revOStream.getArray();
    }

    @Override
    public int encode(ReverseByteArrayOutputStream output) {
        try {
            if (dataCode != null) {
                output.writeByte(dataCode);
                return dataCode.length;
            } else {
                return encodeValue(output);
            }
        } catch (IOException e) {
            throw new RuntimeException("Encode error", e);
        }
    }

    /**
     * 编码值
     * 
     * @param output 输出流
     * @return 编码长度
     */
    public int encodeValue(ReverseByteArrayOutputStream output) {
        try {
            if (lengthFixed) {
                int codeLength = numOfBytesOf(minVal, maxVal);
                writeValToStream(output, codeLength);
                return codeLength;
            } else {
                int codeLength = numOfBytes(val);
                writeValToStream(output, codeLength);
                output.write((codeLength & 0xFF) | 0x80);
                return codeLength + 1;
            }
        } catch (IOException e) {
            throw new RuntimeException("Encode value error", e);
        }
    }

    /**
     * 将值写入流
     * 
     * @param output     输出流
     * @param codeLength 编码长度
     * @throws IOException IO异常
     */
    private void writeValToStream(ReverseByteArrayOutputStream output, int codeLength) throws IOException {
        for (int i = 0; i < codeLength; i++) {
            int value = (int) ((val >> (8 * i)) & 0xFF);
            output.write(value);
        }
    }

    /**
     * 计算最小值和最大值所需的字节数
     * 
     * @param minVal 最小值
     * @param maxVal 最大值
     * @return 所需字节数
     */
    private int numOfBytesOf(long minVal, long maxVal) {
        return Math.max(numOfBytes(minVal), numOfBytes(maxVal));
    }

    /**
     * 计算值所需的字节数
     * 
     * @param val 值
     * @return 所需字节数
     */
    private int numOfBytes(long val) {
        long sVal = val;
        if (val < 0) {
            sVal = -val - 1;
        }
        return (int) Math.ceil((Math.floor(log2(sVal) + 1) / 8.0));
    }

    /**
     * 计算以2为底的对数
     * 
     * @param sVal 值
     * @return 对数值
     */
    private double log2(long sVal) {
        return Math.log(sVal) / Math.log(2);
    }

    /**
     * 设置值（带验证）
     * 
     * @param val 值
     */
    private void setValue(long val) {
        if (lengthFixed && minVal != 0 && minVal > val) {
            throw new IllegalArgumentException("val is smaller than minimum");
        }
        if (lengthFixed && maxVal != 0 && maxVal < val) {
            throw new IllegalArgumentException("val is greater than maximum");
        }
        this.val = val;
    }

    /**
     * 获取最大值
     * 
     * @return 最大值
     */
    public long getMax() {
        return maxVal;
    }

    /**
     * 获取最小值
     * 
     * @return 最小值
     */
    public long getMin() {
        return minVal;
    }

    /**
     * 获取值
     * 
     * @return 当前值
     */
    public long getValue() {
        return val;
    }

    /**
     * 是否为无符号
     * 
     * @return true如果为无符号
     */
    public boolean isUnsigned() {
        return unsigned;
    }

    /**
     * 是否长度固定
     * 
     * @return true如果长度固定
     */
    public boolean isLengthFixed() {
        return lengthFixed;
    }

    /**
     * 获取数据编码
     * 
     * @return 数据编码
     */
    public byte[] getDataCode() {
        return dataCode;
    }

    @Override
    public String toString() {
        return String.valueOf(val);
    }
}